package generate

import (
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"reflect"
	"strings"
)

func Parse(file string) (fset *FileSet, err error) {
	fset = &FileSet{
		File:    file,
		Specs:   make(map[string]ast.Expr),
		Structs: make(map[string]Struct),
	}

	sfset := token.NewFileSet()
	if err != nil {
		return nil, err
	}

	// parser.Trace
	sfile, err := parser.ParseFile(sfset, file, nil, 0)
	if err != nil {
		return nil, err
	}

	fset.Package = sfile.Name.Name
	fset.Imports = append(fset.Imports, sfile.Imports...)

	for i := range sfile.Decls {
		if g, ok := sfile.Decls[i].(*ast.GenDecl); ok {
			for _, s := range g.Specs {
				if ts, ok := s.(*ast.TypeSpec); ok {
					switch ts.Type.(type) {
					case *ast.StructType:
						fset.Specs[ts.Name.Name] = ts.Type
					}
				}
			}
		}
	}

	for name, def := range fset.Specs {
		switch def := def.(type) {
		case *ast.StructType:
			fset.GetFields(name, def.Fields)
		default:
			log.Fatal("Unknow Type")
		}
	}

	return fset, nil
}

type FileSet struct {
	File    string              // file name
	Package string              // package name
	Specs   map[string]ast.Expr // type specs in file
	Structs map[string]Struct   // processed from specs
	Imports []*ast.ImportSpec   // imports
}

// Example:
// type User struct {
// 	Name string `sql:name`
// }
//
// var Info = &Struct{
// 	Name: "User",
// 	Elems: map[string]Elem{
// 		"Name": Elem{
// 			Name: "Name",
// 			Tag:  "name",
// 			Type: "string",
// 		},
// 	},
// }

type Struct struct {
	Name  string
	Elems map[string]Elem
}

type Elem struct {
	Name string
	Tag  string
	Type string
}

func (fset *FileSet) GetFields(name string, fields *ast.FieldList) {
	struc := Struct{
		Name:  name,
		Elems: make(map[string]Elem, fields.NumFields()),
	}

	for _, field := range fields.List {
		elem := Elem{
			Name: field.Names[0].Name,
			Tag:  field.Names[0].Name,
		}

		if field.Tag != nil {
			body := reflect.StructTag(strings.Trim(field.Tag.Value, "`")).Get("msg")
			tags := strings.Split(body, ",")
			// ignore "-" fields
			if tags[0] == "-" {
				continue
			}
			elem.Tag = tags[0]
		}

		switch t := field.Type.(type) {
		case *ast.Ident:
			elem.Type = t.Name
		default:
			log.Fatal("Unknow Type")
		}

		struc.Elems[elem.Name] = elem
	}

	fset.Structs[name] = struc
}
